/**
 * Copyright (C) 2009+ Magic Zhao
 *		 2008+ Spark Zheng
 *
 * @file	src/serialize.c
 * @brief	c struct serialize functions:
 *		-persistence serialize
 *			-A. phpserialize
 *			-B. xml(xml-parse)
 *			-C. json(json-c)
 *		-memory serialze
 *
 * @module      dlib
 * @see		dlib
 *
 * @history
 *	version 0.1.0, spark init serialize module
 *	version 0.2.0, spark move source-code from magic cext
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#include "hash.h"
#include "serialize.h"

/**
 * PHP serialize,
 * support int, double, string, array in php
 * APIs: dlib_php_serialize, dlib_php_unserialize
 */

/**
 * _map_pair_hash
 * @brief get hash value for dlib_php_mappair_t
 * @return hash value of the key
 * @param key pointer to dlib_php_mappair_t, otherwise this function may cause coredump
 * @param arg unused
 */
static uint32_t _map_pair_hash(const void *key, void *arg);

/**
 * _map_pair_compare
 * @brief compare if key1 & key2 are equal
 * @return 0 indicate the two keys are equal, nonzero otherwise
 * @param key1 first key to be compared
 * @param key2 second key to be compared
 */
static int _map_pair_compare(const void *key1, const void *key2);

static uint32_t _map_pair_hash(const void *key, void *arg)
{
	UNUSED_PARAM(arg);
	dlib_php_string_t *str;
	const dlib_php_mappair_t *pair = (const dlib_php_mappair_t *)key;

	switch (pair->key_type) {
	case DLIB_PHP_INT:
		return (uint32_t)pair->key_ptr;
	case DLIB_PHP_STRING:
		str = (dlib_php_string_t *)pair->key_ptr;
		return js_hash(str->val, str->len);
	default:
		return 0;
	}
}

static int _map_pair_compare(const void *key1, const void *key2)
{
	dlib_php_string_t *str1;
	dlib_php_string_t *str2;
	const dlib_php_mappair_t *pair1 = (const dlib_php_mappair_t *)key1;
	const dlib_php_mappair_t *pair2 = (const dlib_php_mappair_t *)key2;

	if (pair1->key_type == pair2->key_type) {
		switch (pair1->key_type) {
		case DLIB_PHP_INT:
			return ((int)pair1->key_ptr) - ((int)pair2->key_ptr);
		case DLIB_PHP_STRING:
			str1 = (dlib_php_string_t *)pair1->key_ptr;
			str2 = (dlib_php_string_t *)pair2->key_ptr;
			if (str1->len == str2->len) {
				return memcmp(str1->val, str2->val, str1->len);
			} else {
				return str1->len - str2->len;
			}
		default:
			return -1;
		}
	}
	return -1;
}


dlib_php_map_t *php_alloc_map(dlib_alloc_t *na, int capacity)
{
	dlib_php_map_t *mp =
		(dlib_php_map_t *)dlib_naive_alloc(na, DLIB_PMAP_SIZE(capacity));
	if (mp != NULL) {
		mp->map = (htable_naive_t *)ht_naive_init(na, capacity,
				_map_pair_hash, NULL, _map_pair_compare);
		if (mp->map == NULL) {
			return NULL;
		}

		mp->capacity = capacity;
		mp->child_count = 0;
		mp->allocator = na;
	}
	return mp;
}



int php_encode_double(dlib_php_double_t *dbt, char *buf, size_t buf_len)
{
	return dlib_snprintf(buf, buf_len, "d:%f;", dbt->val);
}

int php_encode_int(int value, char *buf, size_t buf_len)
{
	return dlib_snprintf(buf, buf_len, "i:%d;", value);
}

int php_encode_string(dlib_php_string_t *str, char *buf, size_t buf_len)
{
	size_t size = dlib_snprintf(buf, buf_len, "s:%d:\"", str->len);
	if(buf_len > (str->len + size + 2))
	{
		memcpy(&buf[size], str->val, str->len);
		size += str->len;
		buf[size] = '"';
		buf[size + 1] = ';';
		return size + 2;
	}
	return 0;
}

int php_encode_map(dlib_php_map_t *mp, char *buf, size_t buf_len)
{
	int i;
	int encoded_size = dlib_snprintf(buf, buf_len, "a:%d:{", mp->child_count);

	for (i = 0; i < mp->child_count; i++) {
		/* serialize each child here */
		dlib_php_mappair_t *pr = &mp->child_list[i];

		switch (pr->key_type) {
		case DLIB_PHP_INT:
			encoded_size += php_encode_int((int)pr->key_ptr,
					&buf[encoded_size], buf_len - encoded_size);
			break;
		case DLIB_PHP_STRING:
			encoded_size += php_encode_string((dlib_php_string_t *)pr->key_ptr,
					&buf[encoded_size], buf_len - encoded_size);
			break;
		default:
			/* other type can not be key */
			return 0;
		}

		switch (pr->val_type) {
		case DLIB_PHP_INT:
			encoded_size += php_encode_int((int)pr->val_ptr,
					&buf[encoded_size], buf_len - encoded_size);
			break;
		case DLIB_PHP_DOUBLE:
			encoded_size += php_encode_double((dlib_php_double_t *)(pr->val_ptr),
					&buf[encoded_size], buf_len - encoded_size);
			break;
		case DLIB_PHP_STRING:
			encoded_size += php_encode_string((dlib_php_string_t *)(pr->val_ptr),
					&buf[encoded_size], buf_len - encoded_size);
			break;
		case DLIB_PHP_MAP:
			encoded_size += php_encode_map((dlib_php_map_t *)(pr->val_ptr),
					&buf[encoded_size], buf_len - encoded_size);
			break;
		default:
			/* other type not supported */
			return 0;
		}
	}

	buf[encoded_size] = '}';
	encoded_size++;
	//buf[encoded_size] = ';';
	//encode_size++;

	return encoded_size;
}

/**
 * php_encode_map_with_length
 * @warning the protocol: length(%08d)data
 */
int php_encode_map_with_length(dlib_php_map_t *mp, char *buf, size_t buf_len)
{
#ifndef _DLIB_PROTOCOL_HEAD_LENGTH
#define _DLIB_PROTOCOL_HEAD_LENGTH	8
#define _DLIB_PROTOCOL_FORMAT		"%08d"
#endif
	if (buf_len <= (_DLIB_PROTOCOL_HEAD_LENGTH + 1)) {
		return 0;
	}

	int res_len = php_encode_map(mp, &buf[_DLIB_PROTOCOL_HEAD_LENGTH],
			buf_len - _DLIB_PROTOCOL_HEAD_LENGTH);
	if (res_len > 0) {
		char tmp = buf[_DLIB_PROTOCOL_HEAD_LENGTH];
		snprintf(buf, _DLIB_PROTOCOL_HEAD_LENGTH + 1,
			 _DLIB_PROTOCOL_FORMAT, res_len);
		buf[_DLIB_PROTOCOL_HEAD_LENGTH] = tmp;
		return res_len + _DLIB_PROTOCOL_HEAD_LENGTH;
	}
#ifdef _DLIB_PROTOCOL_HEAD_LENGTH
#undef _DLIB_PROTOCOL_HEAD_LENGTH
#undef _DLIB_PROTOCOL_FORMAT
#endif
	return res_len;
}


int php_decode_int(char *encoded_str, int encoded_len, int *val, dlib_alloc_t *na)
{
	char *end_ptr;
	UNUSED_PARAM(na);
	if (encoded_len > 2 && encoded_str[0] == 'i' && encoded_str[1] == ':') {
		*val = strtol(&encoded_str[2], &end_ptr, 10);
		if (end_ptr > encoded_str && end_ptr - encoded_str < encoded_len &&
				*end_ptr == ';') {
			return end_ptr + 1 - encoded_str;
		}
	}
	return 0;
}

int php_decode_double(char *encoded_str, int encoded_len, dlib_php_double_t *dbl, dlib_alloc_t *na)
{
	char *end_ptr;
	UNUSED_PARAM(na);
	if (encoded_len > 2 && encoded_str[0] == 'd' && encoded_str[1] == ':') {
		dbl->val = strtod(&encoded_str[2], &end_ptr);
		if (end_ptr > encoded_str && end_ptr - encoded_str < encoded_len &&
				*end_ptr == ';') {
			return end_ptr + 1 - encoded_str;
		}
	}
	return 0;
}

int php_decode_string(char *encoded_str, int encoded_len, dlib_php_string_t *str, dlib_alloc_t *na)
{
	char *end_ptr;
	UNUSED_PARAM(na);
	if (encoded_len > 2 && encoded_str[0] == 's' && encoded_str[1] == ':') {
		str->len = strtol(&encoded_str[2], &end_ptr, 10);
		int pos = end_ptr - encoded_str;
		if (pos > 0 && (pos + 2) < encoded_len && *end_ptr == ':' &&
				end_ptr[1] == '"') {
			pos += 2;
			str->val = &encoded_str[pos];
			pos += str->len;
			if (encoded_str[pos] == '"' && encoded_str[pos + 1] == ';') {
				return pos + 2;
			}
		}
	}
	return 0;
}

int php_decode_map(char *encoded_str, int encoded_len, dlib_php_map_t **mp, dlib_alloc_t *na)
{
	int pos = 0;
	char *end_ptr;
	dlib_php_map_t *nmp;

	/* started by a:<count>:{ */
	if (encoded_len > 2 && encoded_str[0] == 'a' && encoded_str[1] == ':') {
		/* caculate array size */
		int array_size = strtol(&encoded_str[2], &end_ptr, 10);
		if (array_size > 0) {
			*mp = php_alloc_map(na, array_size);
			if (!(*mp)) {
				return 0;
			}
		} else {
			/* zero sized array is not allowed */
			return 0;
		}
		nmp = *mp;

		/* caculate current position of encoded_string */
		pos = end_ptr - encoded_str;
		if (!(pos > 0 && encoded_str[pos] == ':' && encoded_str[pos + 1] == '{')) {
			return 0;
		}
		pos += 2;

		/* decode each key=>value pair */
		int tmp_value;
		dlib_php_string_t *str;
		dlib_php_double_t *dbl;
		dlib_php_map_t *cmp;
		while (pos < encoded_len && nmp->child_count < array_size) {
			tmp_value = 0;
			str = NULL;
			dbl = NULL;
			cmp = NULL;

			switch (php_decode_get_type(&encoded_str[pos])) {
			case DLIB_PHP_INT:
				pos += php_decode_int(&encoded_str[pos],
						encoded_len - pos, &tmp_value, na);
				nmp->child_list[nmp->child_count].key_ptr = (void *)tmp_value;
				nmp->child_list[nmp->child_count].key_type = DLIB_PHP_INT;
				break;
			case DLIB_PHP_STRING:
				str = (dlib_php_string_t *)dlib_naive_alloc(na, sizeof(*str));
				if (str == NULL) {
					return 0;
				}

				pos += php_decode_string(&encoded_str[pos],
						encoded_len - pos, str, na);
				nmp->child_list[nmp->child_count].key_ptr = str;
				nmp->child_list[nmp->child_count].key_type = DLIB_PHP_STRING;
				break;
			default:
				return 0;
			} /* end switch */

			switch (php_decode_get_type(&encoded_str[pos])) {
			case DLIB_PHP_INT:
				pos += php_decode_int(&encoded_str[pos],
						encoded_len - pos, &tmp_value, na);
				nmp->child_list[nmp->child_count].val_type = DLIB_PHP_INT;
				nmp->child_list[nmp->child_count].val_ptr = (void *)tmp_value;;
				break;
			case DLIB_PHP_STRING:
				str = (dlib_php_string_t *)dlib_naive_alloc(na, sizeof(*str));
				if (str == NULL) {
					return 0;
				}

				pos += php_decode_string(&encoded_str[pos],
						encoded_len - pos, str, na);
				nmp->child_list[nmp->child_count].val_ptr = str;
				nmp->child_list[nmp->child_count].val_type = DLIB_PHP_STRING;
				break;
			case DLIB_PHP_DOUBLE:
				dbl = (dlib_php_double_t *)dlib_naive_alloc(na, sizeof (*dbl));
				if (dbl == NULL) {
					return 0;
				}
				pos += php_decode_double(&encoded_str[pos],
						encoded_len - pos, dbl, na);
				nmp->child_list[nmp->child_count].val_ptr = dbl;
				nmp->child_list[nmp->child_count].val_type = DLIB_PHP_DOUBLE;
				break;
			case DLIB_PHP_MAP:
				pos += php_decode_map(&encoded_str[pos],
						encoded_len - pos, &cmp, na);
				if (cmp == NULL) {
					return 0;
				}
				nmp->child_list[nmp->child_count].val_ptr = cmp;
				nmp->child_list[nmp->child_count].val_type = DLIB_PHP_MAP;
				break;
			default:
				return 0;
			}

			/* add item=>index to hash map */
			ht_naive_insert(nmp->map, &nmp->child_list[nmp->child_count],
				  &nmp->child_list[nmp->child_count]);
			nmp->child_count++;
		} /* end switch */
	} /* end while */

	if (encoded_str[pos] == '}') {
		return pos + 1;
	}

	return 0;
}

int php_serialize(dlib_php_map_t *mp, char *buf, size_t buf_len)
{
	return php_encode_map(mp, buf, buf_len);
}

int php_unserialize(char *buf, size_t buf_len, dlib_php_map_t **mp, dlib_alloc_t *na)
{
	return php_decode_map(buf, buf_len, mp, na);
}
